#pragma once
#include "stdafx.h"
#include "resource.h"

#include <atldataobj.h>		// CClipboard class by Bjarke Viksoe (bjarke@viksoe.dk)

#include <vector>
using std::vector;

#include <string>
using std::wstring;

#include <sstream>
using std::wistringstream;
using std::wostringstream;

#include <iostream>
#include <boost/algorithm/string.hpp>

#include <tuple>
using std::tuple;
using std::make_tuple;

#include <array>
using std::array;

const wchar_t COMMA = L',';
const wstring LINEEND = L"\r\n";

array<wstring,6> SelCmdNames = { 
	L"nsel", L"esel", L"ksel", L"lsel", L"asel", L"vsel" 
};

array<wstring,6> SelItems = { 
	L"node", L"elem", L"kp", L"line", L"area", L"volu"
};

array<wstring,30> IgnoredCmds = {
	L"/dist",
	L"/replot", L"/rep", L"/replo", 
	L"/focus", L"/foc",
	L"kplot", L"lplot", L"aplot", L"vplot", L"gplot", L"eplot",
	L"/user",
	//L"/com",
	L"/pnum",
	L"/input",
	L"/number", L"/num",
	L"/zoom",
	//L"chkmsh",
	L"/auto",
	L"/angle", L"/ang",
	L"/view",
	L"klist", L"llist", L"alist", L"vlist", 
	L"kdist"
};

bool IsStrStartWith(const wstring& str, const wstring& start)
{
	return str.substr(0,start.size())==start;
}

template< typename T >
bool StrToNum(const std::wstring& str, T& num)
{
	std::wistringstream iss(str);
	return !(iss>>num).fail();
}

const COLORREF EDIT_BKGND_COLOR = RGB(204,232,207);

class CMainDialog :
	public CDialogImpl<CMainDialog>,
	public CMessageFilter,
	public CWinDataExchange<CMainDialog>,
	public CDialogResize<CMainDialog>
{
public:
	enum { IDD = IDD_MAIN };

	BEGIN_MSG_MAP(CMainDialog)
		MSG_WM_CTLCOLOREDIT(OnCtlColorEdit)
		COMMAND_HANDLER_EX(IDC_BUTTON_PARSE, BN_CLICKED, OnBnClickedParse)
		COMMAND_HANDLER_EX(IDC_BUTTON_CLEAR, BN_CLICKED, OnBnClickedClear)
		COMMAND_ID_HANDLER_EX(IDCANCEL,OnCancel)
		MSG_WM_INITDIALOG(OnInitDialog)
		MSG_WM_DESTROY(OnDestroy)
		CHAIN_MSG_MAP(CDialogResize<CMainDialog>)
	END_MSG_MAP()

	BEGIN_DDX_MAP(CMainDialog)
		DDX_CONTROL_HANDLE(IDC_EDIT_INPUT,	m_EditInput)
		DDX_CONTROL_HANDLE(IDC_EDIT_OUTPUT, m_EditOutput)
	END_DDX_MAP()

	BEGIN_DLGRESIZE_MAP(CMainDialog)
		DLGRESIZE_CONTROL(IDC_BUTTON_PARSE,	DLSZ_MOVE_X|DLSZ_MOVE_Y)
		DLGRESIZE_CONTROL(IDC_BUTTON_CLEAR,	DLSZ_MOVE_X|DLSZ_MOVE_Y)
		BEGIN_DLGRESIZE_GROUP()
			DLGRESIZE_CONTROL(IDC_EDIT_INPUT,	DLSZ_SIZE_X)
			DLGRESIZE_CONTROL(IDC_EDIT_OUTPUT,	DLSZ_SIZE_X)			
		END_DLGRESIZE_GROUP()
		DLGRESIZE_CONTROL(IDC_EDIT_INPUT,	DLSZ_SIZE_Y)
		DLGRESIZE_CONTROL(IDC_EDIT_OUTPUT,	DLSZ_SIZE_Y)	
	END_DLGRESIZE_MAP()

public:
	virtual BOOL PreTranslateMessage(MSG* pMsg)
	{
		return CWindow::IsDialogMessage(pMsg);
	}

public:
	BOOL OnInitDialog(CWindow /*wndFocus*/, LPARAM /*lInitParam*/)
	{
		// Initialize dialog resize
		DlgResize_Init();

		// Bind controls to variables
		DoDataExchange(FALSE);

		InitIgnoredCmdList();
		InitControls();

		ResizeClient(800,600);
		CenterWindow();

		return TRUE;
	}

	void InitControls() 
	{
		CLogFont lf;
		TCHAR fontName[] = _T("Courier New");
		SecureHelper::strcpy_x(lf.lfFaceName,32,fontName);
		lf.lfHeight = 18;
		m_FontEdit.CreateFontIndirect(&lf);

		m_EditInput.SetFont(m_FontEdit);
		m_EditInput.SetLimitText(0);
		m_EditOutput.SetFont(m_FontEdit);
		m_EditOutput.SetLimitText(0);

		m_BrushEdit.CreateSolidBrush(EDIT_BKGND_COLOR);
		ATL_CHECK_HANDLE(m_BrushEdit);
	}

	void InitIgnoredCmdList()
	{
		for (auto it=SelCmdNames.cbegin();it!=SelCmdNames.cend();it++)
		{
			m_vecIgnoredSelCmd.push_back(make_tuple( *it, 3, L"p51x" ));
		}
		m_vecIgnoredSelCmd.push_back(make_tuple( L"cm",	    0, L"_y" ));
		m_vecIgnoredSelCmd.push_back(make_tuple( L"cmsel",  1, L"_y" ));
		m_vecIgnoredSelCmd.push_back(make_tuple( L"cmdele", 0, L"_y" ));
	}

	void OnDestroy() { }

	void OnBnClickedParse(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl*/)
	{
		Parse();
	}

	void OnBnClickedClear(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl*/)
	{
		m_EditInput.SetWindowText(_T(""));
		m_EditOutput.SetWindowText(_T(""));
	}

	void OnCancel(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl*/)
	{
		DestroyWindow();
		PostQuitMessage(0);
	}

	HBRUSH OnCtlColorEdit(CDCHandle dc, CEdit /*edit*/)
	{
		dc.SetBkColor(EDIT_BKGND_COLOR);
		return m_BrushEdit;
	}

private:
	enum AnsysSelType
	{
		UnknownType = 0,
		NodeNumber = 1,
		ElementNumber,
		KeypointNumber,
		LineNumber,
		AreaNumber,
		VolumeNumber
	};

	enum AnsysCmdType
	{
		OrdinaryCmd,
		FLST,
		FITEM,
		IgnoredCmd,
		IgnoredSelCmd,
		DeleLccaCmd,
		Comment,
		BlankLine
	};

	void Parse() 
	{
		ATL::CString text;
		m_EditInput.GetWindowText(text);
		wstring inputStr = text;
		ParseAnsysCmd(inputStr);
	}

	void ParseAnsysCmd(const wstring& inputStr)
	{
		ATLASSERT(inputStr.size()>0);
		wostringstream oss;

		AnsysSelType selType = UnknownType;
		AnsysCmdType lastCmdType = OrdinaryCmd;
		AnsysCmdType thisCmdType = OrdinaryCmd;

		wistringstream iss(inputStr);
		wstring cmdLine;
		while (std::getline(iss,cmdLine))
		{
			wstring cmdName;
			vector<wstring> args;

			boost::trim(cmdLine);
			if (cmdLine.empty())	// blank line
			{
				thisCmdType = BlankLine;
			}
			else if (cmdLine[0]==L'!')	// comments
			{
				thisCmdType = Comment;
			}
			else
			{
				boost::to_lower(cmdLine);
				GetCmdName(cmdLine,cmdName);
				if (IsIgnoredCmd(cmdName))
				{
					ATLASSERT(lastCmdType!=FLST);
					thisCmdType = IgnoredCmd;
				}
				else
				{
					GetCmdArgs(cmdLine,args);
					if (IsIgnoredSelCmd(cmdName,args))	// ignored select command
					{
						ATLASSERT(lastCmdType!=FLST);
						thisCmdType = IgnoredSelCmd;
					}
					else if (cmdName == L"flst")		// "FLST" command
					{
						ATLASSERT(lastCmdType!=FLST);
						thisCmdType = FLST;

						int num = 0;
						bool res = StrToNum(args[2],num);
						ATLASSERT(res && 1<=num && num<=6);
						selType = (AnsysSelType)num;
					}
					else if (cmdName == L"fitem")		// "FITEM" command
					{
						ATLASSERT((lastCmdType==FLST) || (lastCmdType==FITEM));
						thisCmdType = FITEM;

						int selNum = 0;
						bool res = StrToNum(args[1],selNum);
						ATLASSERT(res && selNum!=0);
						if (selNum > 0)
						{
							const wstring&  cmd = SelCmdNames[selType-1];
							wstring        type = (lastCmdType==FLST)? L"s":L"a";
							const wstring& item = SelItems[selType-1];

							oss	<<LINEEND
								<<cmd<<COMMA<<type<<COMMA<<item<<COMMA<<COMMA<<selNum;
						}
						else	// if selNum < 0
						{
							oss<<COMMA<<(-selNum);
						}
					}
					else if (cmdName == L"*set" && IsStrStartWith(args[1],L"lsinqr"))
					{
						ATLASSERT(lastCmdType!=FLST);
						thisCmdType = DeleLccaCmd;

						const int JUMP_LINES = 13;
						for (int i=0;i<JUMP_LINES;++i)
							std::getline(iss,cmdLine);

						oss <<LINEEND<<LINEEND
							<<L"lsel,s,lcca"<<LINEEND
							<<L"ldele,all"<<LINEEND;
					}
					else		// other ordinary command
					{
						ATLASSERT(lastCmdType!=FLST);
						thisCmdType = OrdinaryCmd;

						oss<<LINEEND<<cmdName;
						// replace "p51x" and "_yN" label with "all", 
						// then output all args
						std::for_each(args.begin(),args.end(),[&oss](wstring& arg){
							if (IsStrStartWith(arg,L"p51x")||IsStrStartWith(arg,L"_y")){
								arg = L"all";
							}
							oss<<COMMA<<arg;
						});
					}
				}
				lastCmdType = thisCmdType;
				continue;	// we have output this command here, 
							// then we need to process the next one directly
			}

			ATLASSERT(thisCmdType==BlankLine || thisCmdType==Comment);
			if (lastCmdType==FITEM || lastCmdType==OrdinaryCmd)
			{
				oss<<LINEEND;
			}
			lastCmdType = thisCmdType;
		}

		m_EditOutput.SetWindowText(oss.str().c_str());

		CClipboard clipBoard(NULL);
		ATLASSERT(clipBoard.IsOpen());
		clipBoard.Empty();
		clipBoard.SetUnicodeTextData(oss.str().c_str());
	}

	bool IsSelCmd(const wstring& cmdName)
	{
		return std::any_of(SelCmdNames.cbegin(),SelCmdNames.cend(),
			[&cmdName](const wstring& name)
		{
			return cmdName==name;
		});
	}

	bool IsIgnoredCmd(const wstring& cmdName)
	{
		return std::any_of(IgnoredCmds.cbegin(),IgnoredCmds.cend(),
			[&cmdName](const wstring& name)
		{
			return cmdName==name;
		});
	}

	bool IsIgnoredSelCmd(const wstring& cmdName, const vector<wstring>& args)
	{
		return std::any_of(m_vecIgnoredSelCmd.cbegin(),m_vecIgnoredSelCmd.cend(),
			[&cmdName,&args](const tuple<wstring,int,wstring>& t)->bool
		{
			const wstring& name = std::get<0>(t);
			int i = std::get<1>(t);
			const wstring& arg = std::get<2>(t);
			return (cmdName==name) && (args.size()>i) && IsStrStartWith(args[i],arg);
		});
	}

	void GetCmdName(const wstring& cmdLine, wstring& cmdName)
	{
		ATLASSERT(!cmdLine.empty() && cmdName.empty());

		std::wistringstream iss(cmdLine);
		std::getline(iss,cmdName,COMMA);
		boost::trim(cmdName);
		ATLASSERT(!cmdName.empty());
	}

	void GetCmdArgs(const wstring& cmdLine, vector<wstring>& args)
	{
		ATLASSERT(!cmdLine.empty() && args.empty());

		std::wistringstream iss(cmdLine);
		wstring cmdName;
		std::getline(iss,cmdName,COMMA);
		
		wstring arg;
		while (std::getline(iss,arg,COMMA))
		{
			boost::trim(arg);
			args.push_back(arg);
		}
	}

private:
	CFont m_FontEdit;
	CEdit m_EditInput;
	CEdit m_EditOutput;

	CBrush m_BrushEdit;

	vector<tuple<wstring,int,wstring>> m_vecIgnoredSelCmd;
};
